;|----------------------|
;|	100000 ~ END	|
;|	   KERNEL	|
;|----------------------|
;|	E0000 ~ 100000	|
;| Extended System BIOS |
;|----------------------|
;|	C0000 ~ Dffff	|
;|     Expansion Area   |
;|----------------------|
;|	A0000 ~ bffff	|
;|   Legacy Video Area  |
;|----------------------|
;|	9F000 ~ A0000	|
;|	 BIOS reserve	|
;|----------------------|
;|	90000 ~ 9F000	|
;|	 kernel tmpbuf	|
;|----------------------|
;|	10000 ~ 90000	|
;|	   LOADER	|
;|----------------------|
;|	8000 ~ 10000	|
;|	  VBE info	|
;|----------------------|
;|	7E00 ~ 8000	|
;|	  mem info	|
;|----------------------|
;|	7C00 ~ 7E00	|
;|	 MBR (BOOT)	|
;|----------------------|
;|	0000 ~ 7C00	|
;|	 BIOS Code	|
;|----------------------|


org 10000h
    jmp Label_Start

%include "fat32.inc"

; 内核加载到0x0:0x100000地址，1M地址
BaseOfKernelFile        equ         0x0
OffsetOfKernelFile      equ         0x100000

; 内核临时存放地址，BIOS中断服务在实模式下只支持1MB一下物理地址空间寻址，因此需要中转一下
BaseTmpOfKernelAddr     equ         0x9000
OffsetTmpOfKernelFile   equ         0x0000

; 内核加载完成后，该地址不再使用，用来存放内存相关信息
MemoryStructBufferAddr  equ         0x7e00

[SECTION gdt]
LABEL_GDT:          dd  0,0                         ; 第一项必须为null
LABEL_DESC_CODE32:  dd  0x0000FFFF,0x00CF9A00       ; 第二项为代码段
LABEL_DESC_DATA32:  dd  0x0000FFFF,0x00CF9200       ; 第三项为数据段

GdtLen                  equ         $ - LABEL_GDT
GdtPtr                  dw          GdtLen - 1      ; GDT表长度
    dd LABEL_GDT                                    ; GDT表基地址

; 段选择子
SelectorCode32          equ         LABEL_DESC_CODE32 - LABEL_GDT
SelectorData32          equ         LABEL_DESC_DATA32 - LABEL_GDT

[SECTION gdt64]
LABEL_GDT64:        dq  0x0000000000000000          ; 第一项必须为null
LABEL_DESC_CODE64:  dq  0x0020980000000000          ; 第二项为代码段
LABEL_DESC_DATA64:  dq  0x0000920000000000          ; 第三项为数据段

GdtLen64                equ         $ - LABEL_GDT64
GdtPtr64                dw          GdtLen64 - 1
    dd LABEL_GDT64

; 64位段选择子
SelectorCode64          equ         LABEL_DESC_CODE64 - LABEL_GDT64
SelectorData64          equ         LABEL_DESC_DATA64 - LABEL_GDT64

[SECTION .s16]  ; 定义一个名为.s16的段
[BITS 16]       ; 表示指令运行在16位宽下，使用32位宽数据指令时需要在指令前加入前缀0x66，使用32位宽地址指令时需要在指令前加入前缀0x67

Label_Start:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x00                ; 栈地址0x10000:0x0

    mov ax, 0b800h
    mov gs, ax

; ========== display on screen : Start Loader......
    mov ax, 1301h
    mov bx, 000fh              
    mov dx, 0200h               ; 第2行
    mov cx, 12
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, StartLoaderMessage
    int 10h

    ;jmp $

; ========== open address A20
    push ax
    in al, 92h
    or al, 00000010b
    out 92h, al
    pop ax
    
    cli                         ; 开启A20功能后，关闭外部中断

    db 0x66                     ; 使用32位数据指令，需要在指令前加上0x66前缀               
    lgdt [GdtPtr]               ; 加载保护模式结构数据信息

    mov eax, cr0                ; 通过cr0第0位开启保护模式
    or eax, 1
    mov cr0, eax

    mov ax, SelectorData32      ; ax = SelectorData32
    mov fs, ax                  ; fs = SelectorData32
    mov eax, cr0                ; 关闭保护模式，只要不对段寄存器重新赋值，实模式下也会按照保护模式下的寻址方式寻址
    and al, 11111110b
    mov cr0, eax


; ========== search kernel.bin
    mov word [SectorNo], SectorNumOfRootDirStart    ; 根目录起始扇区号存到SectorNo变量中                                                                               
Label_Search_In_Root_Dir_Begin:
    mov eax, 0                          ; eax = 0
    mov es, ax                          ; es = 0
    mov bx, 8000h                       ; bx = 0x8000
    mov ax, [SectorNo]                  ; ax = SectorNo，第一个根目录扇区
    mov ecx, 1                          ; ecx = 1
    call Func_ReadOneSector             ; 根目录扇区加载到0x8000
    mov si, KernelFileName              ; si = KernelFileName的起始地址
    mov di, 8000h                       ; di = 0x8000
    cld                                 ; DF位置0，表示从低地址向高地址读取
    mov dx, 10h                         ; dx = 0x10，记录每个扇区容纳的目录项个数，一个目录项32字节，512/32=16=0x10
Label_Search_For_LoaderBin:
    cmp dx, 0                           ; if (dx == 0)
    jz Label_Goto_Next_Sector_In_Root_Dir    ; 如果dx == 0成立，表示当前扇区搜索完成，跳转Label_Goto_NextSector_In_Root_Dir循环执行下一个扇区的搜索
    dec dx                              ; dx减1
    mov cx, 11                          ; cx = 11，记录文件名长度，总共11字节
Label_Cmp_FileName:
    cmp byte [es:di], 0                 ; if 根目录项第一个字节为0表示搜索结束
    jz Label_No_LoaderBin               ; 没有找到文件
    cmp cx, 0                           ; if (cx == 0)
    jz Label_FileName_Found             ; 如果cx == 0成立，表示找到loader.bin，跳转到Label_FileName_Found
    dec cx                              ; cx减1
    lodsb                               ; 从ds:si读取1个字节数据到al，si加1，依次读取文件名到al
    cmp al, byte [es:di]                ; al中保存文件名，es:di保存从扇区读取的数据
    jz Label_Go_On                      ; 相等跳转到Label_Go_On继续对比
    jmp Label_Different                 ; 不相等跳转到Label_Diferent

Label_Go_On:
    inc di                              ; 前面判断成立，di加1
    jmp Label_Cmp_FileName              ; 跳转到Label_Cmp_FileName循环对比文件名
Label_Different:                        ; 文件名不相同
    and di, 0ffe0h                      ; di = di & 0xffe0，每个目录项32字节，当前目录项没有找到，di & 0xffe0回到目录项的起始地址
    add di, 20h                         ; di = di + 0x20，di指向下一个目录项
    mov si, KernelFileName              ; si重新指向文件名起始地址
    jmp Label_Search_For_LoaderBin      ; 跳转到Label_Search_For_LoaderBin继续匹配文件名
Label_Goto_Next_Sector_In_Root_Dir:
    add word [SectorNo], 1              ; SectorNo变量加1
    jmp Label_Search_In_Root_Dir_Begin  ; 跳转到Label_Search_In_Root_Dir_Begin开始新的扇区搜索

; =========== display on screen: ERROR:No KERNEL Found
Label_No_LoaderBin:                     ; 同前面显示StartBootMessage
    mov ax, 1301h
    mov bx, 008ch
    mov dx, 0300h                       ; 第三行
    mov cx, 21
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, NoLoaderMessage
    int 10h
    jmp $

; ========== found kernel.bin name in root director struct
Label_FileName_Found:
    xor ecx, ecx
    and di, 0ffe0h                      ; di指向匹配到的目录项首地址
    add di, 0x14                        ; di指向文件起始簇号的高两个字节
    mov cx, word [es:di]                ; 读取起始簇号高两个字节
    shl ecx, 16
    and di, 0ffe0h                      ; di指向匹配到的目录项首地址
    add di, 01ah                        ; di = di + 0x1a，di指向起始簇号低两个字节
    mov cx, word [es:di]                ; 读取起始簇号保存到cx
    and di, 0ffe0h
    add di, 01ch                        ; di指向匹配到的目录项的文件大小
    mov eax, dword [es:di]              ; eax = 文件大小
    mov word [KernelFileSize], ax       ; 这里默认文件大小不超过65535
    push ecx                            ; ecx压栈
    mov eax, 0                          ; eax = 0
    mov al, [BPB_SecPerClus]            ; al = BPB_SecPerClus, cx中保存起始簇号，乘BPB_SecPerClus得到起始逻辑扇区号
    mul ecx                             ; 起始逻辑扇区号保存到eax
    mov ecx, eax                        ; ecx = eax
    add ecx, SectorBalance              ; ecx加上SectorBalance找到对应数据区物理扇区号
    mov ax, BaseTmpOfKernelAddr         ; ax保存kernel文件临时保存段地址0x0
    mov es, ax                          ; 文件保存段地址赋值给es
    mov bx, OffsetTmpOfKernelFile       ; bx保存kernel文件临时保存的偏移地址，现在文件保存地址为es:bx
    mov eax, ecx                        ; 前面计算的数据区物理扇区号保存到eax
Label_Go_On_Loading_File:
    ; INT 10h, AH=0Eh功能：在屏幕上显示一个字符
    ;       AL = 待显示的字符
    ;       BL = 前景色
    push eax                            ; eax压栈
    push ebx                            ; ebx压栈
    ; 显示一个字符.
    mov ah, 0eh                         ; ah = 0x0e
    mov al, '.' 
    mov bl, 0fh 
    int 10h 
    ; 恢复eax,ebx
    pop ebx
    pop eax

    mov ch, 0
    mov cl, [BPB_SecPerClus]            ; cx = 0x20，读取一个簇
    call Func_ReadOneSector             ; 调用Func_ReadOneSector读取一个簇
    pop eax                             ; 从栈中取出Label_FileName_Found中push ecx保存的起始簇号

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;        ; 将kernel从临时地址搬移到真实保存地址
    push cx                             ; 保存现场
    push eax
    ; push fs                           ; 物理机上不能修改fs的值，不然fs的寻址能力会回到1MB以内
    push edi
    push ds
    push esi

    xor eax, eax                        ; eax = 0
    mov al, [BPB_SecPerClus]            ; al = BPB_SecPerClus
    mov cx, [BPB_BytesPerSec]           ; cx = 文件大小
    mul cx
    mov ecx, eax                        ; ecx = 一个簇的大小
    mov edi, dword [OffsetOfKernelFileCount]    ; OffsetOfKernelFileCount记录了移动了多少的kernel数据，[fs:edi]指向kernel接下来迁移的目标地址

    mov ax, BaseTmpOfKernelAddr         ; ax = BaseTmpOfKernelAddr
    mov ds, ax                          ; ds保存kernel临时存放地址的段地址
    mov esi, OffsetTmpOfKernelFile      ; esi保存kernel临时存放地址的偏移地址，[ds:esi]指向kernel临时存放地址

Label_Mov_Kernel:                       ; 开始一个字节一个字节地搬迁加载的kernel数据
    mov al, byte [ds:esi]               ; [ds:esi] => al
    mov byte [fs:edi], al               ; al => [fs:edi]
    inc esi                             ; esi加1
    inc edi                             ; edi加1
    loop Label_Mov_Kernel               ; cx减1，如果cx不为0，循环执行Label_Mov_Kernel
    mov eax, 0x1000                     ; eax = 0x1000
    mov ds, eax                         ; 恢复数据段寄存器ds值为0x1000，接下来赋值的变量数据段地址为0x1000
    mov dword [OffsetOfKernelFileCount], edi    ; 更新变量OffsetOfKernelFileCount的值，保证kernel数据连续

    pop esi                             ; 恢复现场
    pop ds
    pop edi
    ; pop fs                            ; 物理机上不能修改fs的值，不然fs的寻址能力会回到1MB以内
    pop eax
    pop cx
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;          ; 继续加载kernel下一个扇区数据
    call Func_GetFATEntry               ; 调用Func_GetFATEntry获取下一个FAT表
    cmp eax, 0fffffffh                  ; 如果取得值为0x0fffffff表示结束簇
    jz Label_File_Loaded                ; 跳转到Label_File_Loaded，文件加载完成
    push eax                            ; 否则保存簇号，用于下一次查找下一个FAT表

    mov cx, 0x0
    mov cl, [BPB_SecPerClus]            ;
    mul cx
    add eax, SectorBalance              ; eax = eax + SectorBalance获得对应数据区扇区号
    jmp Label_Go_On_Loading_File        ; 跳转到Label_Go_On_Loading_File继续读取文件

Label_File_Loaded:
    ; 一种新的显示字符方法，从内存地址0xb800开始，是一段专门用于显示的内存空间，每个字符占两个字节，低字节保存显示的字符，高字节保存字符属性
    mov ax, 0B800h
    mov gs, ax
    mov ah, 0Fh                         ; 0000: 黑底    1111: 白字
    mov al, 'G'
    mov [gs:((80 * 0 + 39) * 2)], ax    ; 屏幕第0行，第39列

; ========== get memory address size type
    mov ax, 1301h
    mov bx, 000Fh
    mov dx, 0400h                       ; 第4行
    mov cx, 44
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, StartGetMemStructMessage    ; 显示StartGetMemStructMessage
    int 10h

    mov ebx, 0                          ; ebx = 0
    mov ax, 0x00                        ; ax = 0
    mov es, ax                          ; es = 0
    mov di, MemoryStructBufferAddr      ; di = MemoryStructBufferAddr，存放内存信息，结构体信息参考kernel中memory.h中st_mem_E820结构体

Label_Get_Mem_Struct:
    mov eax, 0x0e820                    ; eax = 0xe820, INT 15h功能：获取物理地址空间信息
    mov ecx, 20                         ; 预设返回结果的缓存区结构体长度，字节为单位
    mov edx, 0x534d4150                 ; 字符串"SMAP"
    int 15h                             ; 调用BIOS中断
    jc Label_Get_Mem_Fail               ; CF=1说明操作失败，CF=0说明操作成功，失败跳转到Label_Get_Mem_Fail执行
    add di, 20                          ; es:di保存获取的结构体信息，di = di + 20，为获取下一个结构体信息准备

    cmp ebx, 0                          ; ebx = 0表示第一个结构体信息，说明检测结束，否则为下一个结构体信息
    jne Label_Get_Mem_Struct            ; 跳转到Label_Get_Mem_Struct继续获取下一个结构体信息
    jmp Label_Get_Mem_OK                ; 跳转到Label_Get_Mem_OK完成物理地址空间信息获取

Label_Get_Mem_Fail:
    mov ax, 1301h
    mov bx, 008ch
    mov dx, 0500h                       ; 第5行
    mov cx, 23                          ; 字符串长度
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, GetMemStructErrMessage
    int 10h
    jmp $
Label_Get_Mem_OK:
    mov ax, 1301h
    mov bx, 000fh
    mov dx, 0600h                       ; 第6行
    mov cx, 29                          ; 字符串长度
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, GetMemStructOKMessage
    int 10h

; ========== get SVGA information
    mov ax, 1301h
    mov bx, 000fh
    mov dx, 0800h                       ; 第8行
    mov cx, 23
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, StartGetSVGAVBEInfoMessage
    int 10h

    mov ax, 0x00
    mov es, ax
    mov di, 0x8000
    mov ax, 4f00h                       ; INT 10h,ax=4f00h，获取VBE控制器信息, es:di指向VbeInfoBlock信息块结构的起始地址，ax保存VBE返回状态
                                        ; VBE返回状态: al=4fh表示支持该功能，al!=4fh表示不支持该功能，ah=00h表示操作成功，ah=01h表示操作失败，ah=02h表示当前硬件环境不支持该功能，ah=03h表示此功能在当前图像模式下无效
                                        ; struct VbeInfoBlock {
                                        ;   char VbeSignature[4];       // VBE识别标志，固定为"VESA"
                                        ;   uint16_t VbeVersion;        // VBE版本（BCD码）
                                        ;   uint32_t OemStringPtr;      // OEM字符串指针
                                        ;   uint8_t Capabilities[4];    // 图形控制器的机能
                                        ;   uint32_t VideoModePtr;      // VideoModeList指针
                                        ;   uint16_t TotalMemory;       // 64KB内存块数量
                                        ;   uint16_t OemSoftwareRev;    // VBE软件版本（BCD码）
                                        ;   uint32_t OemVerdorNamePtr;  // OEM供应商名字指针
                                        ;   uint32_t OemProductNamePtr; // OEM产品名指针
                                        ;   uint32_t OemProduceRevPtr;  // OEM产品版本指针
                                        ;   uint8_t Reserved[222];      // 保留
                                        ;   uint8_t OemData[256];       // OEM数据
                                        ; };
    int 10h
    cmp ax, 004fh
    jz .KO
; ========== Fail
    mov ax, 1301h
    mov bx, 008ch
    mov dx, 0900h                       ; 第9行
    mov cx, 23
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, GetSVGAVBEInfoErrMessage
    int 10h
    jmp $

.KO:
    mov ax, 1301h
    mov bx, 000fh
    mov dx, 0a00h                       ; 第10行
    mov cx, 29
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, GetSVGAVBEInfoOKMessage
    int 10h

; ========== Get SVGA Mode Info
    mov ax, 1301h
    mov bx, 000fh
    mov dx, 0c00h                       ; 第12行
    mov cx, 24
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, StartGetSVGAModeInfoMessage
    int 10h

    mov ax, 0x00
    mov es, ax
    mov si, 0x8000
    mov cx, 22h

LOOP_Disp_VBE_Info:
; ========== display SVGA mode information
    mov ax, 00h
    mov al, byte [es:si]
    call Func_DispAL
    add si, 1
    
    loop LOOP_Disp_VBE_Info

    mov	cx,	0x55aa
    push ax	
    mov	ax,	00h
    mov	al,	ch
    call Func_DispAL
    mov	ax,	00h
    mov	al,	cl	
    call Func_DispAL	
    pop	ax

    mov cx, 0xff

LABLE_Get_Mode_List:
    add cx, 1
    cmp cx, 0x200
    jz LABEL_Get_Mode_Finish

    mov ax, 4f01h                           ; INT 10H, ax=4f01h，获取VBE模式信息, cx=模式号，es:di指向ModeInfoBlock结构的起始地址, ax保存VBE返回状态
                                            ; VBE返回状态: al=4fh表示支持该功能，al!=4fh表示不支持该功能，ah=00h表示操作成功，ah=01h表示操作失败，ah=02h表示当前硬件环境不支持该功能，ah=03h表示此功能在当前图像模式下无效
                                            ; struct ModeInfoBlock {
                                            ;   // 所有版本强制提供的信息
                                            ;   uint16_t ModeAttributes;    // 模式属性
                                            ;   uint8_t WinAAttributes;     // 窗口A属性
                                            ;   uint8_t WinBAttributes;     // 窗口B属性
                                            ;   uint16_t WinGranularity;    // 窗口颗粒度（单位KB）
                                            ;   uint16_t WinSize;           // 窗口大小（单位KB）
                                            ;   uint16_t WinASegment;       // 窗口A的段地址（实模式）
                                            ;   uint16_t WinBSegment;       // 窗口B的段地址（实模式）
                                            ;   uint32_t WinFuncPtr;        // 窗口功能的入口地址（实模式）
                                            ;   uint16_t BytesPerScanLine;  // 每条扫描线占用字节数
                                            ;   // VBE 1.2以上版本强制提供的信息
                                            ;   uint16_t XResolution;       // 水平分辨率（像素或字符）
                                            ;   uint16_t YResolution;       // 垂直分辨率（像素或字符）
                                            ;   uint8_t XCharSize;          // 字符宽度（像素）
                                            ;   uint8_t YCharSize;          // 字符高度（像素）
                                            ;   uint8_t NumberOfPlanes;     // 内存平面数量
                                            ;   uint8_t BitsPerPixel;       // 每像素占用位宽
                                            ;   uint8_t NumberOfBanks;      // 块数量
                                            ;   uint8_t MemoryModel;        // 内存模式类型
                                            ;   uint8_t BankSize;           // 块容量（单位KB）
                                            ;   uint8_t NumberOfImagePages; // 图像页数量
                                            ;   uint8_t Reserved;           // 为分页功能保留
                                            ;   // 直接颜色描画区域（支持Direct Color和YUV内存模式）
                                            ;   uint8_t RedMaskSize;        // Direct Color的红色屏蔽位宽
                                            ;   uint8_t RedFieldPosition;   // 红色屏蔽位的起始位置
                                            ;   uint8_t GreenMaskSize;      // Direct Color的绿色屏蔽位宽
                                            ;   uint8_t GreenFieldPosition; // 绿色屏蔽位的起始位置
                                            ;   uint8_t BlueMaskSize;       // Direct Color的蓝色屏蔽位宽
                                            ;   uint8_t BlueFieldPosition;  // 蓝色屏蔽位的起始位置
                                            ;   uint8_t RsvdMaskSize;       // Direct Color的保留色屏蔽位宽
                                            ;   uint8_t RsvdFieldPosition;  // 保留色屏蔽位的起始位置
                                            ;   uint8_t DirectColorModeInfo;// Direct Color模式属性
                                            ;   // VBE 2.0以上版本强制提供的信息
                                            ;   uint32_t PhysBasePtr;       // 平坦帧缓存区模式的起始物理地址
                                            ;   uint32_t Reserved1;         // 保留，必须为0
                                            ;   uint16_t Reserved2;         // 保留，必须为0
                                            ;   // VBE 3.0以上版本强制提供的信息
                                            ;   uint16_t LinBytesPerScanLine;   // 线性模式的每条扫描线占用字节数
                                            ;   uint8_t BnkNumberOfImagePages;  // 块模式的图像页数量
                                            ;   uint8_t LinNumberOfImagePages;  // 线性模式的图像页数量
                                            ;   uint8_t LinRedMaskSize;         // Direct Color的红色屏蔽位宽（线性模式）
                                            ;   uint8_t LinRedFieldPosition;    // 红色屏蔽位的起始位置（线性模式）
                                            ;   uint8_t LinGreenMaskSize;       // Direct Color的绿色屏蔽位宽（线性模式）
                                            ;   uint8_t LinGreenFieldPosition;  // 绿色屏蔽位的起始位置（线性模式）
                                            ;   uint8_t LinBlueMaskSize;        // Direct Color的蓝色屏蔽位宽（线性模式）
                                            ;   uint8_t LinBlueFieldPosition;   // 蓝色屏蔽位的起始位置（线性模式）
                                            ;   uint8_t LinRsvdMaskSize;        // Direct Color的保留色屏蔽位宽（线性模式）
                                            ;   uint8_t LinRsvdFieldPosition;   // 保留色屏蔽位的起始位置（线性模式）
                                            ;   uint32_t MaxPixelClock;         // 图像模式的最大像素时钟（单位Hz）
                                            ;   uint8_t Reserved3[189];         // ModeInfoBlock剩余空间
                                            ; }__attribute__((packed));
    mov edi, 0x8200
    int 10h

    cmp ax, 004fh
    jnz LABLE_Get_Mode_List

    push ax
    mov ax, 00h
    mov al, ch
    call Func_DispAL
    mov ax, 00h
    mov al, cl
    call Func_DispAL
    pop ax

    jmp LABLE_Get_Mode_List

LABEL_Get_Mode_Finish:
    mov cx, 0x55aa
    push ax
    mov ax, 00h
    mov al, ch
    call Func_DispAL
    mov ax, 00h
    mov al, cl
    call Func_DispAL
    pop ax

    mov cx, 0x144                           ; 查询0x118模式的ModelInfoBlock结构信息
    mov ax, 4f01h
    mov edi, 0x8200
    int 10h

    push ax
    mov ax, 00h
    mov al, ch
    call Func_DispAL
    mov ax, 00h
    mov al, cl
    call Func_DispAL
    pop ax

    mov cx, 0x55aa
    push ax
    mov ax, 00h
    mov al, ch
    call Func_DispAL
    mov ax, 00h
    mov al, cl
    call Func_DispAL
    pop ax

    mov si, 0x8200
    mov cx, 128
LOOP_Disp_Mode_Info:
    mov ax, 00h
    mov al, byte [es:si]
    call Func_DispAL
    add si, 1
    loop LOOP_Disp_Mode_Info
    ;jmp $
    
    jmp Label_SVGA_Mode_Info_Finish

Label_Set_SVGA_Mode_VESA_VBE_FAIL:
    mov ax, 1301h
    mov bx, 008ch                       ; 文字闪烁
    mov dx, 0d00h                       ; 第13行
    mov cx, 27
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, SetSVGAModeInfoVBAVESAMessage
    int 10h
    jmp $

Label_SVGA_Mode_Info_Finish:
    mov ax, 1301h
    mov bx, 000fh
    mov dx, 0e00h                       ; 第14行
    mov cx, 30
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, GetSVGAModeInfoOKMessage
    int 10h

; ========== set the SVGA mode(VESA VBE)
    ; bochs显示模式
    ; 0x180     1440列900行     地址0xe0000000      32位像素点
    ; 0x143     800列600行      地址0xe0000000      32位像素点
    ; virtualbox显示模式
    ; 0x118     1024列768行     地址0xe0000000      24位像素点
    ; 0x144     1024列768行     地址0xe0000000      32位像素点
    mov ax, 4f02h
    mov bx, 4144h                       ; mode: 0x180/0x143
    int 10h

    cmp ax, 004fh
    jnz Label_Set_SVGA_Mode_VESA_VBE_FAIL
    
; ========== init IDT GDT goto protect mode
    mov ax, cs
    mov ds, ax
    mov fs, ax
    mov es, ax

    cli                                 ; 禁用中断

    db 0x66                             ; 前缀，表示当前指令的操作数是32位
    lidt [IDT_POINTER]

    db 0x66                             ; 前缀，表示当前指令的操作数是32位
    lgdt [GdtPtr]                       ; 设置GDT
    
    mov eax, cr0                        ; eax = cr0
    or eax, 1                           ; eax = eax | 1
    mov cr0, eax                        ; cr0 = eax，设置PE位进入保护模式，后面必须跟一个far jmp
    jmp dword SelectorCode32:GO_TO_TMP_Protect  ; 跳转到32位指令区


[SECTION .s32]
[BITS 32]

GO_TO_TMP_Protect:
    
; ========== go to tmp long mode
    mov ax, 0x10                        ; 设置段寄存器，现在段寄存器中存放的是段选择子，等于SelectorData32
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov ss, ax
    mov esp, 7e00h                      ; 设置栈顶
    
    call Func_support_long_mode         ; 调用Func_support_long_mode检测系统是否支持长模式
    test eax, eax                       ; tmp = eax & eax
    jz no_support                       ; 如果tmp为0，跳转到no_support执行

; ========== init temporaty page table 0x90000
    mov dword [0x90000], 0x91007        ; 内存分页，在0x90000地址处建立临时分页表
    mov dword [0x90800], 0x91007
    mov dword [0x91000], 0x92007
    mov dword [0x92000], 0x000083
    mov dword [0x92008], 0x200083
    mov dword [0x92010], 0x400083
    mov dword [0x92018], 0x600083
    mov dword [0x92020], 0x800083
    mov dword [0x92028], 0xa00083

; ========== load GDTR
    db 0x66
    lgdt [GdtPtr64]                     ; 设置64位GDT表
    mov ax, 0x10                        ; 重新设置64位段选择子
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov esp, 7e00h
;    jmp $                               ; 此处除了cs，其它段寄存器的基地址和段限长失效了
; ========== open PAE
    mov eax, cr4                        ; eax = cr4
    bts eax, 5                          ; eax设置第5位
    mov cr4, eax                        ; cr4 = eax，开启PAE

; ========== load cr3
    mov eax, 0x90000                    ; eax = 0x90000，设置分页表地址
    mov cr3, eax                        ; cr3 = eax

; ========== enable long-mode
    mov ecx, 0c0000080h                 ; 访问MSR寄存器，必须利用ECX传入寄存器地址
    rdmsr                               ; 读取IA32_EFER寄存器
    bts eax, 8                          ; 第8位是LME标志位
    wrmsr                               ; 写回IA32_EFER

; ========== open PE and paging
    mov eax, cr0
    bts eax, 0
    bts eax, 31
    mov cr0, eax
    jmp SelectorCode64:OffsetOfKernelFile   ; 虚拟机中执行b 0x100000，执行的时候会在内核开始处暂停，可以看到系统进入64位长模式了

; ========== test support long mode or not
Func_support_long_mode:
    mov eax, 0x80000000                 ; eax = 0x80000000
    cpuid                               ; 调用cpuid指令确定最高的扩展功能参数
    cmp eax, 0x80000001                 ; 只有扩展功能号大于等于0x80000001，才有可能支持长模式
    setnb al                        
    jb support_long_mode_done
    mov eax, 0x80000001
    cpuid
    bt edx, 29
    setc al
support_long_mode_done:
    movzx eax, al
    ret

; ========== no support
no_support:
    jmp $


[SECTION .s16lib]
[BITS 16]

; =========== read one sector from disk
    ; INT 13h, AH=42h功能：lba寻址方式读取磁盘扇区
    ; CL = 读入的扇区数（必须非0）
    ; EAX = LAB方式扇区编号
    ; DL = 驱动器号（如果操作的是硬盘驱动器，bit7必须被置位）
    ; ES:BX = 数据缓冲区
Func_ReadOneSector:
    ; 输入参数：EAX = 待读取的磁盘起始扇区号
    ;           CX = 读入的扇区数量
    ;           ES:BX = 目标缓冲区起始地址
    push dx
    push si
    ;mov dx, ds                          ; 此处ds的值为0x1000，栈的段地址为0x0，因此需要先保留ds值，然后设置为0，int 13h中断不能正确拿到硬盘地址包，这里的问题找了很久，最后通过调试bios代码才发现问题
    ;push dx                             ; 保留ds值
    ;mov dx, 0                           ; dx = 0
    ;mov ds, dx                          ; ds = 0
    push dword 00h                      ; 扇区起始号高4字节
    push dword eax                      ; 扇区起始号
    push word es                        ; 缓存地址段地址
    push word bx                        ; 缓存地址段偏移
    push word cx                        ; 读取扇区个数
    push word 10h                       ; 高字节是预留字节为0，低字节10h表示包结构为16个字节
    mov ax, 4200h                         ; INT 13h，AH=42h功能：磁盘读取,DL=磁盘驱动器号,DS:SI指向硬盘地址包
    mov dl, 0x80
    mov si, sp
    int 13h
    add sp, 10h                         ; 读取完成后要还原栈空间
    ;pop dx                              ; 恢复ds
    ;mov ds, dx
    pop si                              ; 恢复si
    pop dx                              ; 恢复dx
    ret                                 ; 返回

; =========== get FAT Entry

; 硬盘FAT32文件系统分配图
; 数据区                        ?                               数据区不但可以保存目录项信息还可以保存文件内的数据
; 根目录区                      1164~?                          根目录区只能保存目录项信息，为0表示结束
; FAT2                          598~1163                        备份FAT表
; FAT1                          32~597                          FAT表
; 保留扇区，第一个为引导扇区    0~31

; FAT32表项说明
; 第一个表项0x0FFFFFF8，磁盘标示字，低字节与BPB_Media数值保持一致
; 第二个表项0x0FFFFFFF，第一个簇已经被占用
; 第三个表项0x0FFFFFF8
; 0x00000000: 可用簇
; 0xFFFFFFF7: 坏簇
; 0x0FFFFFFF: 文件的最后一个簇

Func_GetFATEntry:
    ; 输入eax = FAT表项号
    ; 输出eax = FAT表项号
    push es                         ; 保存es
    push ebx                         ; 保存ebx
    push eax                         ; 保存eax
    mov ax, 00                      ; ax = 0
    mov es, ax                      ; es = 0
    pop eax                          ; 恢复ax
    mov ebx, 4
    mul ebx

    xor edx, edx                      ; dx = 0
    mov bx, [BPB_BytesPerSec]       ; bx = 512
    div bx                          ; ax = ax / 512，ax得到扇区号，dx得到扇区偏移地址
    push dx                         ; 保存dx
    mov bx, 8000h                   ; bx = 0x8000
    add eax, SectorNumOfFAT1Start   ; FAT1起始扇区号为1，得到FAT1真正扇区号
    mov cx, 1                       ; cx = 1，读取1个扇区的数据
    call Func_ReadOneSector         ; 调用Func_ReadOneSector读取FAT1的扇区数据

    pop dx                          ; 恢复dx
    add bx, dx                      ; bx加上偏移地址，得到表项FAT地址
    mov eax, dword [es:bx]          ; 读取4字节数据
    pop ebx                         ; 恢复ebx
    pop es                          ; 恢复es
    ret                             ; 返回

; ========== display num in al
Func_DispAL:
    ; 输入参数：al = 要转16进制的数据
    ;           DisplayPosition = 显示的位置
    push ecx                        ; 保存现场
    push edx
    push edi

    mov edi, [DisplayPosition]      ; edi = 显示位置
    mov ah, 0Fh                     ; ah = 0xf，字符属性
    mov dl, al                      ; dl = al，保留低4位
    shr al, 4                       ; al = al >> 4，先处理高4位
    mov ecx, 2                      ; ecx = 2，总共两位，需要loop指令再循环一次
.begin:
    and al, 0Fh                     ; al = al & 0xf
    cmp al, 9                       ; al和9比较
    ja .1                           ; 比9大，跳转到.1执行
    add al, '0'                     ; al = al + '0'，得到al数值对应的数字字符
    jmp .2                          ; 跳转到.2执行
.1:
    sub al, 0ah                     ; al = al - 10
    add al, 'A'                     ; al = al + 'A'，得到al数值对应的16进制字符
.2:
    mov [gs:edi], ax                ; al中是显示的字符，ah中是字符属性
    add edi, 2                      ; edi加2，一个字符占两个字节
    mov al, dl                      ; al = dl, 处理低4位
    loop .begin                     ; cx = cx - 1，不为0跳转到.begin处理

    mov [DisplayPosition], edi      ; 更新下一个显示位置

    pop edi                         ; 恢复现场
    pop edx
    pop ecx
    ret

; ========== tmp IDT
IDT:
    times   0x50    dq  0           ; 临时IDT结构，切换过程禁用了中断，无须完整初始化IDT表
IDT_END:

IDT_POINTER:
    dw  IDT_END - IDT - 1
    dd  IDT

; =========== tmp variable
    SectorNo                dw      0
    KernelFileSize          dw      0
    OffsetOfKernelFileCount dd      OffsetOfKernelFile
    DisplayPosition         dd      0

; ========== display messages
StartLoaderMessage:         db      "Start Loader"
NoLoaderMessage:            db      "ERROR:No KERNEL Found"
KernelFileName:             db      "KERNEL  BIN",0     ; FAT12文件名占8字节，扩展名占3字节，没有'.'
StartGetMemStructMessage:   db      "Start Get Memory Struct (address,size,type)."
GetMemStructErrMessage:     db      "Get Memory Struct Error"
GetMemStructOKMessage:      db      "Get Memory Struct Successful!"

StartGetSVGAVBEInfoMessage: db  "Start Get SVGA VBE Info"
GetSVGAVBEInfoErrMessage:   db  "Get SVGA VBE Info ERROR"
GetSVGAVBEInfoOKMessage:    db  "Get SVGA VBE Info SUCCESSFUL!"

StartGetSVGAModeInfoMessage:    db  "Start Get SVGA Mode Info"
GetSVGAModeInfoOKMessage:   db  "Get SVGA Mode Info SUCCESSFUL!"

SetSVGAModeInfoVBAVESAMessage:  db  "Set SVGA Mode VBE VESA Fail"
